namespace AgentJohnson
{
  using System;
  using System.Collections.Generic;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.Expressions;
  using Resharper.Abstractions.CodeDom.Expressions.BinaryExpressions.AdditiveExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.LiteralExpressions;
  using Resharper.Abstractions.CodeDom.Statements;
  using Resharper.Abstractions.CodeDom.Statements.Blocks;

  /// <summary>Defines the <see cref="UseStringBuilder"/> class.</summary>
  [ContextAction("Use StringBuilder [Agent Johnson]", "Converts concatenation of a few strings and other objects to the use of StringBuilder class.")]
  public class UseStringBuilder : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="UseStringBuilder"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public UseStringBuilder([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    /// <exception cref="System.ArgumentNullException">context</exception>
    protected override bool CanExecute(CanExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.GetModel(context) != null;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    /// <exception cref="System.ArgumentNullException">context</exception>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = this.GetModel(context);
      if (model == null)
      {
        return;
      }

      var statement = context.Factory.CreateStatement("StringBuilder stringBuilder = new StringBuilder();");

      model.Block.AddStatementBefore(statement, model.Anchor);

      foreach (var expression in model.Expressions)
      {
        statement = context.Factory.CreateStatement("stringBuilder.Append(" + expression + ");");

        model.Block.AddStatementBefore(statement, model.Anchor);
      }

      var toString = context.CreateExpression("stringBuilder.ToString()");
      if (toString == null)
      {
        return;
      }

      model.AdditiveExpression.ReplaceBy(toString);

      context.Formatter.Format(model.Block);
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Use StringBuilder [Agent Johnson]";
    }

    /// <summary>Builds the addition list.</summary>
    /// <param name="expression">The expression.</param>
    /// <param name="expressions">The expressions.</param>
    /// <returns>Returns <c>true</c>, if successful, otherwise <c>false</c>.</returns>
    private bool GetExpressions([NotNull] Expression expression, [NotNull] List<Expression> expressions)
    {
      if (expression.TypeName == "string")
      {
        var literalExpression = expression as LiteralExpression;
        if (literalExpression != null)
        {
          expressions.Add(expression);
          return true;
        }

        var additiveExpression = expression as AdditiveExpression;
        if (additiveExpression != null)
        {
          var left = this.GetExpressions(additiveExpression.LeftOperand, expressions);
          var right = this.GetExpressions(additiveExpression.RightOperand, expressions);

          return left || right;
        }
      }

      expressions.Add(expression);

      return false;
    }

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    /// <exception cref="System.ArgumentNullException">context</exception>
    private Model GetModel([NotNull] ProviderContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var anchor = context.GetSelectedStatement();
      if (anchor == null)
      {
        return null;
      }

      var block = anchor.GetContainingBlock();
      if (block == null)
      {
        return null;
      }

      var additiveExpression = context.GetContainingAdditiveExpression();
      if (additiveExpression == null)
      {
        return null;
      }

      if (additiveExpression.TypeName != "string")
      {
        return null;
      }

      var expressions = new List<Expression>();

      if (!this.GetExpressions(additiveExpression, expressions))
      {
        return null;
      }

      foreach (var expression in expressions)
      {
        var literalExpression = expression as LiteralExpression;

        if (literalExpression == null || literalExpression.TypeName != "string")
        {
          return null;
        }
      }

      return new Model
      {
        AdditiveExpression = additiveExpression,
        Expressions = expressions,
        Block = block,
        Anchor = anchor
      };
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    private class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the expression.
      /// </summary>
      /// <value>The expression.</value>
      [NotNull]
      public AdditiveExpression AdditiveExpression { get; set; }

      /// <summary>
      /// Gets or sets the anchor.
      /// </summary>
      /// <value>The anchor.</value>
      public Statement Anchor { get; set; }

      /// <summary>
      /// Gets or sets the block.
      /// </summary>
      /// <value>The block.</value>
      public Block Block { get; set; }

      /// <summary>
      /// Gets or sets the expressions.
      /// </summary>
      /// <value>The expressions.</value>
      public List<Expression> Expressions { get; set; }

      #endregion
    }
  }
}


